package org.filecollector.filecontroller.virtualcatalogue.virtualfile;

import lombok.NonNull;

import java.nio.file.Path;
/**
 * Class implements {@link VirtualFile} interface
 * <p>
 *     All business logic encapsulated into {@link VirtualFileStaticMethods} methods.
 * </p>
 */
class VirtualFileClass implements VirtualFile {

    @NonNull final VirtualFileDependencies dependencies;
    @NonNull final VirtualFileDao data;

    /**
     * Send debug message to {@link VirtualFileDependencies#debugger()} depending on {@code  checkVar} status
     * @param checkVar variable that defines which string will be sent
     * @param ifTrue will be sent to {@link VirtualFileDependencies#debugger()} if {@code checkVar} is true
     * @param ifFalse will be sent to {@link VirtualFileDependencies#debugger()} if {@code checkVar} is false
     */
    private void debugMessage(boolean checkVar, String ifTrue, String ifFalse) {
        VirtualFileStaticMethods.debugMessage(data.innerFile().getPath(), dependencies.debugger(),
                checkVar, ifTrue, ifFalse);
    }

    /**
     * Send debug message to {@link VirtualFileDependencies#debugger()}
     * @param message will be sent to {@link VirtualFileDependencies#debugger()}
     */
    private void debugMessage(String message) {
        debugMessage(true, message, message);
    }

    /**
     * Method checks existing of inner file
     * @throws InvalidFileException with flag {@link FileTroubles#FILE_DISAPPEARED}
     */
    private void checkExisting() {
        if (isExists()) {
            return;
        }
        throw new InvalidFileException(
                "Invalid file",
                FileTroubles.FILE_DISAPPEARED,
                data.innerFile().toPath()
        );
    }

    /**
     * Checking existence of File
     * @return true if file exists and not null; false in others cases
     */
    @Override
    public final boolean isExists() {
        return VirtualFileStaticMethods.throwableChecker(
                () -> VirtualFileStaticMethods.exists(data.innerFile(), dependencies.debugger())
        );
    }

    /**
     * Getting id
     * @return id
     */
    @Override
    public final @NonNull Long getId() {
        return VirtualFileStaticMethods.throwableChecker(data::id);
    }

    private VirtualFileClass() {
        throw new RuntimeException("Empty initializer");
    }

    protected VirtualFileClass(VirtualFileDao data, VirtualFileDependencies dependencies) {
        this.data = data;
        this.dependencies = dependencies;
    }

    /**
     * Getting of file size
     * @return size of file in bytes
     */
    @Override
    public final Long size() {
        checkExisting();
        return VirtualFileStaticMethods.throwableChecker(
            () -> {
                try {
                    return data.innerFile().length();
                } catch (SecurityException se) {
                    throw new InvalidFileException(
                        String.format(
                            "Cannot get access to file %s due to security troubles. Message: %s",
                            data.innerFile().getPath(),
                            se.getMessage()
                        ),
                        FileTroubles.SECURITY_TROUBLES,
                        data.innerFile().toPath()
                    );
                }
            }
        );
    }

    /**
     * Getting directly access to object's dependencies
     * @return dependencies
     * @apiNote It is not recommended to get access to inner fields besides specific interface methods. Shall be used
     * only for tests.
     */
    @Override
    public final VirtualFileDependencies getDependencies() {
        return dependencies;
    }

    /**
     * Getting directly access to object's data
     * @return data
     * @apiNote It is not recommended to get access to inner fields besides specific interface methods. Shall be used
     * only for tests.
     */
    @Override
    public final VirtualFileDao getDao() {
        return data;
    }

    /**
     * Getting path of encapsulated file.
     * @return {@link Path} to the inner file;
     * @throws InvalidFileException <ul>
     *     <li>with flag {@link FileTroubles#FILE_DISAPPEARED} if inner file is no more available for any reasons</li>
     * </ul>
     */
    @Override
    public final Path getPath() {
        checkExisting();
        debugMessage("send filepath");
        return VirtualFileStaticMethods.throwableChecker(() -> data.innerFile().toPath());
    }

    /**
     * Calculate checksum by the methods defined in {@link VirtualFileDependencies}
     * @return Checksum as Long
     * @throws InvalidFileException <ul>
     *     <li>with flag {@link FileTroubles#FILE_DISAPPEARED} if inner file is no more available for any reasons</li>
     * </ul>
     */
    @Override
    public Long calculateCheckSum() {
        checkExisting();
        return VirtualFileStaticMethods.throwableChecker(
                () -> VirtualFileStaticMethods.getCheckSum(
                        dependencies.checkSumCounter(), data.innerFile(), dependencies.warner()
                )
        );
    }

    /**
     * Directly writing to file from buffer
     * @param buffer chars that will be written into file
     * @param append allow to not rewrite file
     * @return true if success, false in other cases
     * @throws InvalidFileException <ul>
     *     <li>with flag {@link FileTroubles#FILE_DISAPPEARED} if inner file is no more available for any reasons</li>
     * </ul>
     */
    @Override
    public boolean writeDirectly(char[] buffer, boolean append) {
        checkExisting();
        return VirtualFileStaticMethods.throwableChecker(
                () -> VirtualFileStaticMethods.writeDirectly(
                        buffer, data.innerFile(), append, dependencies.warner(), this::debugMessage
                )
        );
    }

    /**
     * Check if File exists and creates it
     * @return true if file exists or created; false in other cases
     */
    public boolean touch() {
        return VirtualFileStaticMethods.throwableChecker(
                () -> VirtualFileStaticMethods.touch(
                        data.innerFile().getPath(), dependencies.inform(), dependencies.warner()
                )
        );
    }

    /**
     * Directly reading from file into buffer of {@code size} or less
     * @param size is <i>maximum</i> size of further buffer
     * @return char buffer that contains read head of inner file of size equals to {@code size} or less
     * @throws InvalidFileException <ul>
     *     <li>with flag {@link FileTroubles#FILE_DISAPPEARED} if inner file is no more available for any reasons</li>
     *     <li>with flag {@link FileTroubles#UNREADABLE} if inner file cannot be read</li>
     * </ul>
     */
    @Override
    public final char[] readDirectly(int size) {
        checkExisting();
        return VirtualFileStaticMethods.throwableChecker(
                () -> VirtualFileStaticMethods.readDirectly(
                        size, data.innerFile(), dependencies.warner(), this::debugMessage
                )
        );
    }

    /**
     * Remove inner file
     * @return true if file will be deleted; false in other cases
     * @throws InvalidFileException <ul>
     *     <li>with flag {@link FileTroubles#CANNOT_REMOVE} if inner file not allowed to remove</li>
     *     </ul>
     */
    @Override
    public boolean remove() {
        checkExisting();
        return VirtualFileStaticMethods.throwableChecker(
                () -> {
                    try {
                        return data.innerFile().delete();
                    } catch (SecurityException se) {
                        throw new InvalidFileException(
                            String.format("File %s can not be removed", data.innerFile().getPath()),
                            FileTroubles.CANNOT_REMOVE,
                            data.innerFile().toPath()
                        );
                    }
                }
        );
    }
}
